//
//	File Name   :	Dx10Renderer.cpp
//	Description :	Dx10Renderer implementation file.
//	Author		:	William McVicar
//  Mail		:	mcvicar.william@gmail.com
//

//  Precompiled Header
#include "CommonDefines.h"
#ifdef DX10

//  Library Includes

//  Local Includes
#include "ProteinFramework.h"
#include "RendererData.h"
#include "Mesh.h"
#include "Clock.h"
#include "IndexBuffer.h"
#include "IndexBufferBase.h"
#include "VertexBuffer.h"
#include "VertexBufferBase.h"
#include "VertexFormatBase.h"
#include "VertexFormat.h"

//  This includes
#include "Dx10Renderer.h"

//	Macros

typedef struct tagVS_CONSTANT_BUFFER
{
	D3DXMATRIX mWorldViewProj;

} tVS_CONSTANT_BUFFER;


D3DXMATRIXA16 g_World;
D3DXMATRIXA16 g_View;
D3DXMATRIXA16 g_Proj;
ID3D10Buffer* g_pConstantTable = NULL;

//  Static Variables

//  Static Function Prototypes

//  Implementation
#define SAFE_RELEASE(x) { if(x) x->Release(); x = NULL; } 

namespace Protein
{

Dx10Renderer::Dx10Renderer()
: m_driverType		     ( D3D10_DRIVER_TYPE_NULL )
, m_pDevice			       ( NULL )
, m_pSwapChain		     ( NULL )
, m_pRenderTargetView  ( NULL )
, m_pDepthStencil	     ( NULL )
, m_pDepthStencilView  ( NULL )
, m_pActiveVertexShader( NULL )
, m_pActivePixelShader ( NULL )
{
	//
}

Dx10Renderer::~Dx10Renderer()
{
	delete m_pRendererData;
	m_pRendererData = NULL;

	if( m_pDevice )
		m_pDevice->ClearState();

	SAFE_RELEASE( m_pRenderTargetView );
	SAFE_RELEASE( m_pDepthStencilView );
	SAFE_RELEASE( m_pDepthStencil );
	SAFE_RELEASE( m_pSwapChain );
	SAFE_RELEASE( m_pDevice );
}

HRESULT Dx10Renderer::Initialise( HWND hWnd )
{
	HRESULT hr = S_OK;

	RECT rc;
	GetClientRect( hWnd, &rc );
	UINT width = rc.right - rc.left;
	UINT height = rc.bottom - rc.top;
	UINT createDeviceFlags = 0;
#ifdef _DEBUG
	createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
#endif

	DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof( sd ) );
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;

	hr = D3D10CreateDeviceAndSwapChain( NULL, 
										D3D10_DRIVER_TYPE_HARDWARE, 
										NULL, createDeviceFlags, 
										D3D10_SDK_VERSION, &sd, 
										&m_pSwapChain, &m_pDevice );

	if( FAILED( hr ) )
		return hr;

	ID3D10Texture2D* pBackBuffer = NULL;
	hr = m_pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), (LPVOID*)&pBackBuffer );
	if( FAILED( hr ) )
		return hr;

	hr = m_pDevice->CreateRenderTargetView( pBackBuffer, NULL, &m_pRenderTargetView );
	pBackBuffer->Release();
	if( FAILED( hr ) )
		return hr;

	//create depth stencil texture
	D3D10_TEXTURE2D_DESC descDepth;
	memset( &descDepth, 0, sizeof( descDepth ) );
	descDepth.Width = width;
    descDepth.Height = height;
    descDepth.MipLevels = 1;
    descDepth.ArraySize = 1;
    descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    descDepth.SampleDesc.Count = 1;
    descDepth.SampleDesc.Quality = 0;
    descDepth.Usage = D3D10_USAGE_DEFAULT;
    descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
    descDepth.CPUAccessFlags = 0;
    descDepth.MiscFlags = 0;
    hr = m_pDevice->CreateTexture2D( &descDepth, NULL, &m_pDepthStencil );
    if( FAILED( hr ) )
        return hr; 

    // Create the depth stencil view
    D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
	ZeroMemory( &descDSV, sizeof(descDSV) );
    descDSV.Format = descDepth.Format;
    descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
    descDSV.Texture2D.MipSlice = 0;
	hr = m_pDevice->CreateDepthStencilView( m_pDepthStencil, &descDSV, &m_pDepthStencilView );
    if( FAILED( hr ) )
        return hr;

	m_pDevice->OMSetRenderTargets( 1, &m_pRenderTargetView, NULL );

	D3D10_VIEWPORT vp;
	vp.Width = width;
	vp.Height = height;
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	m_pDevice->RSSetViewports( 1, &vp );

	m_pRendererData = new RendererData();
	m_pRendererData->m_pDevice = m_pDevice;

	ShowWindow( hWnd, SW_SHOW );

	hr = Stuff();
	if( FAILED(hr ) )
		return hr;

	return S_OK;
}

void Dx10Renderer::Render()
{
	Update();

    float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
	m_pDevice->ClearRenderTargetView( m_pRenderTargetView, ClearColor );

	m_pMesh->vertexBuffer()->Enable();
	m_pMesh->vertexFormat()->Enable();
	//m_pDevice->IASetInputLayout( m_pVertexLayout );
	m_pMesh->indexBuffer()->Enable();
	m_pDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

	m_pDevice->VSSetConstantBuffers( 0, 1, &g_pConstantTable );

	m_pDevice->VSSetShader( m_pActiveVertexShader );
	m_pDevice->GSSetShader( NULL );
	m_pDevice->PSSetShader( m_pActivePixelShader );
	m_pDevice->DrawIndexed( m_pMesh->GetNumIndices(), 0, 0 );

	m_pSwapChain->Present( 0, 0 );
}

void Dx10Renderer::Update()
{
	// Update our time
    static float t = 0.0f;
	if( m_driverType == D3D10_DRIVER_TYPE_REFERENCE )
    {
        t += ( float )D3DX_PI * 0.0125f;
    }
    else
    {
        static DWORD dwTimeStart = 0;
        DWORD dwTimeCur = GetTickCount();
        if( dwTimeStart == 0 )
            dwTimeStart = dwTimeCur;
        t = ( dwTimeCur - dwTimeStart ) / 1000.0f;
    }

    //
    // Animate the model
    //
    D3DXMatrixRotationX( &g_World, t );
	D3DXMATRIXA16 mat;
	D3DXMatrixRotationY( &mat, t );
	D3DXMatrixMultiply( &g_World, &g_World, &mat );
	D3DXMatrixRotationZ( &mat, t );
	D3DXMatrixMultiply( &g_World, &g_World, &mat );
	D3DXMATRIXA16 mWorldViewProj;
	mWorldViewProj = g_World * g_View * g_Proj;

	tVS_CONSTANT_BUFFER* pConstData;

	g_pConstantTable->Map( D3D10_MAP_WRITE_DISCARD, NULL, ( void** )&pConstData );
	pConstData->mWorldViewProj = mWorldViewProj;
	g_pConstantTable->Unmap();

}

HRESULT Dx10Renderer::Stuff()
{
    m_pMesh = new Mesh();
	//m_pMesh->Load3DS( "ElephantBody.3ds" );
	m_pMesh->LoadFBX( "monkey.fbx" );
	//m_pMesh->Load("cube");

	//Compile vs file
	std::string strPath;
	fileSystem().FileExists( "default.vs", strPath );
	LPD3D10BLOB pVSBuf = NULL;

	DWORD dwShaderFlags = 0;
#ifdef _DEBUG 
	dwShaderFlags |=  D3D10_SHADER_DEBUG;
#endif

	HRESULT hr = D3DX10CompileFromFileA( strPath.c_str(), NULL, NULL, "VS", "vs_4_0", dwShaderFlags, NULL, NULL, &pVSBuf, NULL, NULL );  
	hr = m_pDevice->CreateVertexShader( ( DWORD* )pVSBuf->GetBufferPointer(), pVSBuf->GetBufferSize(), &m_pActiveVertexShader );
	m_pMesh->SetVertexShader( pVSBuf );

	//compile the PS file
	fileSystem().FileExists( "default.ps", strPath );
	LPD3D10BLOB pPSBuf = NULL;

	hr = D3DX10CompileFromFileA( strPath.c_str(), NULL, NULL, "PS", "ps_4_0", dwShaderFlags, NULL, NULL, &pPSBuf, NULL, NULL );
	m_pDevice->CreatePixelShader( ( DWORD* )pPSBuf->GetBufferPointer(), pPSBuf->GetBufferSize(), &m_pActivePixelShader );

	pVSBuf->Release();
	pVSBuf = NULL;
	pPSBuf->Release();
	pPSBuf = NULL;

	// Create a constant buffer
	D3D10_BUFFER_DESC cbDesc;
	cbDesc.ByteWidth = sizeof( tVS_CONSTANT_BUFFER );
    cbDesc.Usage = D3D10_USAGE_DYNAMIC;
    cbDesc.BindFlags = D3D10_BIND_CONSTANT_BUFFER;
    cbDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    cbDesc.MiscFlags = 0;
	m_pDevice->CreateBuffer( &cbDesc, NULL, &g_pConstantTable );

	// Initialize the world matrix
    D3DXMatrixIdentity( &g_World );

    // Initialize the view matrix
    D3DXVECTOR3 Eye( 0.0f, -1.0f, -10.0f );
    D3DXVECTOR3 At( 0.0f, 1.0f, 0.0f );
    D3DXVECTOR3 Up( 0.0f, 1.0f, 0.0f );
    D3DXMatrixLookAtLH( &g_View, &Eye, &At, &Up );

    // Initialize the projection matrix
	D3DXMatrixPerspectiveFovLH( &g_Proj, ( float )D3DX_PI * 0.5f, 640 / ( FLOAT )480, 0.1f, 100000.0f );

	return S_OK;
}

}

#endif //DX10